/*
 * Universidade Federal do Rio Grande do Norte.
 * Departamento de Computação.
 * Sistemas Digitais.
 * Sistema de controle de plantas.
 * 26/08/2011
 */
package interfaceplantaserial;
import gnu.io.CommPortIdentifier;
import gnu.io.PortInUseException;
import gnu.io.SerialPort;
import gnu.io.SerialPortEvent;
import gnu.io.SerialPortEventListener;
import gnu.io.UnsupportedCommOperationException;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.Collection;
import java.util.Enumeration;
import java.util.LinkedList;
import java.util.TooManyListenersException;
import java.util.logging.Level;
import java.util.logging.Logger;

/**
 * Classe para abstrair os métodos do driver de comunicacao RXTX.
 * @author J.Felipe
 */
public class ConexaoSerial implements Runnable, SerialPortEventListener {
    
    /** Atributo para guardar as portas disponíveis. */
    private Enumeration portasDisponiveis;
    /** Atributo para guardar a porta ativa. */
    private SerialPort portaAtiva;
    /** Atributo para guardar a taxa de transmissão da porta. */
    private int baudRate;
    /** Atributo para guardar o timeout da porta. */
    private int timeOut;
    /** Atributo para guardar os data bits da porta. */
    private int dataBits;
    /** Atributo para guardar os stop bits da porta. */
    private int stopBit;
    /** Atributo para guardar a paridade da porta. */
    private int paridade;
    /** Atributo para guardar o que será enviado pela porta ativa.*/
    private OutputStream output;
    /** Atributo para guardar o que será recebido pela porta ativa.*/
    private InputStream input;
    /** Thread para leitura. */
    private Thread readingThread;
    /** Flag para indicar se existe alguma porta em uso. */
    private boolean conexaoAtiva;
    /** Atributo para salvar a mensagem recebida pela porta ativa. */
    private String mensagem;
    

    /**
     * Contrutor da classe.
     */
    public ConexaoSerial(int baudRate, int timeOut) {
        portasDisponiveis = CommPortIdentifier.getPortIdentifiers();
        this.baudRate = baudRate;
        this.timeOut = timeOut;
        this.dataBits = portaAtiva.DATABITS_8;
        this.stopBit = portaAtiva.STOPBITS_1;
        this.paridade = portaAtiva.PARITY_NONE;
    }
    /**
     * Retorna todas as portas disponíveis.
     * @return 
     */
    public Collection<String> getPortasDisponiveis(){
        Collection<String> listaDePortas = new LinkedList<String>();
        while (portasDisponiveis.hasMoreElements()){
            CommPortIdentifier porta = (CommPortIdentifier)portasDisponiveis.nextElement();
            listaDePortas.add(porta.getName());
        }
        portasDisponiveis = CommPortIdentifier.getPortIdentifiers();
        return listaDePortas;
    }
    /**
     * Método para abrir conexão com uma determinada porta.
     * @param p
     * @return 
     */
    public boolean abrirConexao(String p) throws PortInUseException, UnsupportedCommOperationException {
        boolean flag = false;
        while (portasDisponiveis.hasMoreElements()) {
            CommPortIdentifier porta = (CommPortIdentifier) portasDisponiveis.nextElement();
            if (p.equals(porta.getName())) {
                portasDisponiveis = CommPortIdentifier.getPortIdentifiers();
                portaAtiva = (SerialPort) porta.open(p, timeOut);
                portaAtiva.setSerialPortParams(baudRate,
                        dataBits, stopBit,paridade);
                portaAtiva.setFlowControlMode(SerialPort.FLOWCONTROL_NONE);
                conexaoAtiva = true;
                flag = true;
                break;
            }
        }
        return flag;
    }
    
    /**
     * Método para fechar conexão com a porta ativa.
     * @param p
     * @return 
     */
    public void fecharConexao(){
        if (conexaoAtiva) {
            portaAtiva.close();
            conexaoAtiva = false;
            portasDisponiveis = CommPortIdentifier.getPortIdentifiers();
        }
    }
    
    /**
     * Método para ler os dados da porta ativa.
     * @throws IOException
     * @throws TooManyListenersException 
     */
    public void lerPorta() throws IOException, TooManyListenersException{
        input = portaAtiva.getInputStream();
        portaAtiva.addEventListener(this);
        portaAtiva.notifyOnDataAvailable(true);
        readingThread = new Thread(this);
        readingThread.start();
        run();
    }
    
    /**
     * Método para escrever na porta ativa.
     * 
     * @param comando
     * @param valor
     * @throws IOException
     * @throws InterruptedException 
     */
    public void escreverPorta(byte comando, float valor) throws IOException, InterruptedException{
        escreverPorta(comando, (int)(valor * 1000));
    }
    
    /**
     * Método para escrever na porta ativa.
     * 
     * @param comando
     * @param valor
     * @throws IOException
     * @throws InterruptedException 
     */
    public void escreverPorta(byte comando, int valor) throws IOException, InterruptedException{
        output = portaAtiva.getOutputStream();
            
        byte[] array = new byte[4];
        
//        output.write(new byte[]{ comando }); //0b00000111
        
        
        
        array[0] = (byte) (valor & 0xFF);
        array[1] = (byte) ((valor >> 8) & 0xFF);
        array[2] = (byte) ((valor >> 16) & 0xFF);
        array[3] = (byte) ((valor >> 24) & 0xFF);
        //array[4] = comando;
        
        output.write(array); //0b00000111
        Thread.sleep(100);
//        for (byte b : array) {
//            Thread.sleep(100);
            
//            output.write(new byte[]{ b }); //0b00000111
//        }
        
        //array = new byte[] { 0, 0, 0, 23, 23 };
        //output.write(array); //0b00000111
        
        output.flush(); 
    }

    @Override
    public void run() {
        try {
            Thread.sleep(5);
        } catch (InterruptedException ex) {
            Logger.getLogger(ConexaoSerial.class.getName()).log(Level.SEVERE, null, ex);
        }
    }

    @Override
    public void serialEvent(SerialPortEvent spe) {
        StringBuffer bufferLeitura = new StringBuffer();
        int novoDado = 0;

        switch (spe.getEventType()) {
            case SerialPortEvent.BI:
            case SerialPortEvent.OE:
            case SerialPortEvent.FE:
            case SerialPortEvent.PE:
            case SerialPortEvent.CD:
            case SerialPortEvent.CTS:
            case SerialPortEvent.DSR:
            case SerialPortEvent.RI:
            case SerialPortEvent.OUTPUT_BUFFER_EMPTY:
                break;
            case SerialPortEvent.DATA_AVAILABLE:
                //Novo algoritmo de leitura.
                while (novoDado != -1) {
                    try {
                        novoDado = input.read();
                    } catch (IOException ex) {
                        Logger.getLogger(ConexaoSerial.class.getName()).log(Level.SEVERE, null, ex);
                    }
                    if (novoDado == -1) {
                        break;
                    }
//                    if ('\r' == (char) novoDado) {
//                        bufferLeitura.append('\n');
//                    } else {
                        bufferLeitura.append((char) novoDado);
//                    }
                }
                mensagem = bufferLeitura.toString();
                System.out.println(mensagem);
                break;
        }
    }
    
    public boolean isConexaoAtiva() {
        return conexaoAtiva;
    }
    
    public String getMensagem() {
        return mensagem;
    }
    
    public SerialPort getPortaAtiva() {
        return portaAtiva;
    }
    
    public int getBaudRate() {
        return baudRate;
    }

    public void setBaudRate(int baudRate) {
        this.baudRate = baudRate;
    }

    public int getParidade() {
        return paridade;
    }

    public void setParidade(int paridade) {
        this.paridade = paridade;
    }

    public int getStopBit() {
        return stopBit;
    }

    public void setStopBit(int stopBit) {
        this.stopBit = stopBit;
    }
    
    public int getDataBits() {
        return dataBits;
    }

    public void setDataBits(int dataBits) {
        this.dataBits = dataBits;
    }
}
